﻿using System;
using System.ComponentModel;
using System.Diagnostics;

namespace SimpleDbBrowser.ViewModel
{
    public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
    {
        /// <summary>
        /// Initializes a new instance of the ViewModelBase class.
        /// </summary>
        protected ViewModelBase()
        {
            this.ThrowOnInvalidPropertyName = false;
        }

        /// <summary>
        /// Gets or sets the view model's friendly display name.
        /// </summary>
        public virtual string DisplayName { get; protected set; }

        /// <summary>
        /// Gets a value indicating whether an exception should be thrown when
        /// an invalid property name is provided to the OnPropertyChanged 
        /// event.
        /// </summary>
        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

        /// <summary>
        /// Disposes the view model.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);

            GC.SuppressFinalize(this);
        }
        
        /// <summary>
        /// Fired when a property is changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Verifies that the supplied property name exists as a property on the object.
        /// </summary>
        /// <param name="propertyName">
        /// The property name being checked.
        /// </param>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        private void VerifyPropertyName(string propertyName)
        {
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                var msg = string.Format("Invalid property name: {0}", propertyName);

                if (this.ThrowOnInvalidPropertyName)
                    throw new InvalidOperationException(msg);
                Debug.Fail(msg);
            }

            return;
        }

        /// <summary>
        /// Fires the PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">
        /// The name of the property that has changed.
        /// </param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);

            // Grab it into temp, this may be multi-threaded.
            var handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }

            return;
        }

        /// <summary>
        /// Disposes the view model.
        /// </summary>
        /// <param name="disposing">
        /// True if the view model must be disposed, false if it must be finalized.
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.OnDispose();
            }

            this.OnFinalize();
        }

        /// <summary>
        /// Dispose of the view model.
        /// </summary>
        /// <remarks>
        /// Override this method to perform actions on disposal.
        /// </remarks>
        protected virtual void OnDispose()
        {
        }

        /// <summary>
        /// Finalize the view model.
        /// </summary>
        /// <remarks>
        /// Override this method to perform actions during finalization.
        /// </remarks>
        protected virtual void OnFinalize()
        {
        }

        /// <summary>
        /// Useful for ensuring that ViewModel objects are properly garbage collected.
        /// </summary>
        ~ViewModelBase()
        {
            this.Dispose(false);

#if DEBUG
            var msg = string.Format("{0} ({1}) ({2}) Finalized",
                                       this.GetType().Name,
                                       this.DisplayName,
                                       this.GetHashCode());
            Debug.WriteLine(msg);
#endif

            return;
        }
    }
}

